package cn.yhp.mybatis.builder.xml;

import cn.yhp.mybatis.builder.BaseBuilder;
import cn.yhp.mybatis.datasource.DataSourceFactory;
import cn.yhp.mybatis.io.Resources;
import cn.yhp.mybatis.mapping.BoundSql;
import cn.yhp.mybatis.mapping.Environment;
import cn.yhp.mybatis.mapping.MappedStatement;
import cn.yhp.mybatis.mapping.SqlCommandType;
import cn.yhp.mybatis.session.Configuration;
import cn.yhp.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import javax.sql.DataSource;
import java.io.Reader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * XML配置构建器，建造者模式，继承BaseBuilder
 * @author yhp
 * @date 2023/11/18
 */
public class XMLConfigBuilder extends BaseBuilder {

    private Element root;

    public XMLConfigBuilder(Reader reader){
        // 调用父类初始化Configuration
        super(new Configuration());

        // dom4j 处理xml文件
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new InputSource(reader));
            root= document.getRootElement();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    public Configuration parse(){
        // 获取mappers标签
        Element mappers = root.element("mappers");
        try {
            // 环境
            environmentsElement(root.element("environments"));
            mapperElement(mappers);
        } catch (Exception e) {
            throw new RuntimeException("解析mapper的xml文件异常",e);
        }
        return configuration;
    }

    /**
     * /**  将数据源和事务配置封装进Configuration中
     *      * <environments default="development">
     *      * <environment id="development">
     *      * <transactionManager type="JDBC">
     *      * <property name="..." value="..."/>
     *      * </transactionManager>
     *      * <dataSource type="POOLED">
     *      * <property name="driver" value="${driver}"/>
     *      * <property name="url" value="${url}"/>
     *      * <property name="username" value="${username}"/>
     *      * <property name="password" value="${password}"/>
     *      * </dataSource>
     *      * </environment>
     *      * </environments>
     *
     * @param env
     */
    private void environmentsElement(Element env) throws Exception {
        String environment = env.attributeValue("default");
        List<Element> environmentList = env.elements("environment");
        for (Element e : environmentList) {
            String id = e.attributeValue("id");
            // 如果声明的环境和某配置相同，则获取并启用
            if (environment.equals(id)) {
                String transactionManager = e.element("transactionManager").attributeValue("type");
                // 事务管理器
                TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(transactionManager).newInstance();

                // 数据源
                Element dataSourceElement = e.element("dataSource");
                DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
                List<Element> propertyList = dataSourceElement.elements("property");
                Properties props = new Properties();
                for (Element property : propertyList) {
                    props.setProperty(property.attributeValue("name"), property.attributeValue("value"));
                }
                dataSourceFactory.setProperties(props);
                DataSource dataSource = dataSourceFactory.getDataSource();

                // 构建环境
                Environment environmentEntity = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource).build();

                configuration.setEnvironment(environmentEntity);
            }
        }
    }

    /**
     * 解析mapper.xml的sql，并封装进Configuration中
     * @param mappers
     * @throws Exception
     */
    private void mapperElement(Element mappers) throws Exception {
        // 获取所有的mapper标签
        List<Element> mapperList = mappers.elements("mapper");
        for (Element mapper : mapperList) {
            // 获取mapper的resource属性值，即mapper.xml的路径
            String resource = mapper.attributeValue("resource");
            Reader reader = Resources.getResourceAsReader(resource);
            // 再次用dom4j解析mapper.xml文件
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(new InputSource(reader));

            // 获取xml的根标签，mapper文件中就是mapper标签了
            Element root = document.getRootElement();
            // mapper标签的namespace属性值
            String namespace = root.attributeValue("namespace");

            // 获取mapper标签的select标签
            List<Element> selectNodes = root.elements("select");
            for (Element node : selectNodes) {
                // 获取select语句的信息
                String id = node.attributeValue("id");
                String parameterType = node.attributeValue("parameterType");
                String resultType = node.attributeValue("resultType");
                String sql = node.getText();

                // ？匹配,类似预编译语句
                Map<Integer,String> parameter=new HashMap<>();
                Pattern pattern = Pattern.compile("(#\\{(.*?)})");
                Matcher matcher = pattern.matcher(sql);
                for (int i=1;matcher.find();i++){
                    // 找到#{xxx}
                    String g1 = matcher.group(1);
                    // 找到xxx
                    String g2 = matcher.group(2);
                    parameter.put(i,g2);
                    sql=sql.replace(g1,"?");
                }
                String msId = namespace + "." + id;
                String nodeName = node.getName();
                SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));

                BoundSql boundSql = new BoundSql(sql, parameter, parameterType, resultType);
                MappedStatement mappedStatement = new MappedStatement.Builder(configuration, msId, sqlCommandType, boundSql).build();
                // 将封装好sql信息存入配置项
                configuration.addMappedStatement(mappedStatement);

            }
            // 注册Mapper映射器，namespace值就是mapper接口全包名
            configuration.addMapper(Resources.classForName(namespace));
        }

    }
}
